/*
 *  Controller.java 
 *    
 *    Copyright 2012 YAWP Project Team
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.googlecode.yawp.controller;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Observable;
import java.util.Observer;

import com.googlecode.yawp.model.Preferences;
import com.googlecode.yawp.model.Model;
import com.googlecode.yawp.model.User;
import com.googlecode.yawp.model.WeightEntry;
import com.googlecode.yawp.view.View;
import com.googlecode.yawp.view.View.Answer;



/**
 * This is the controller for the MVC architecture. It process messages 
 * from the view and updates the model, requests views, and applies the 
 * the business logic to the application.
 * 
 * @author Dan Spuches
 * @author Craig Cato
 *
 */
public class Controller implements Observer {
	
	private Model model;						// reference to the model
	private View view;							// reference to the view
	private static final int nameLimit = 20;	// limit of characters for user name
	
	
	/**
	 * Constructor for controller
	 */
	public Controller(Model model, View view) {
		this.model = model;
		this.view = view;
		view.addObserver(this);
		view.showUserLoginView();
	}

	/**
	 * Update for Observer
	 */
	@Override
	public void update(Observable arg0, Object arg1) {
		// this method processes any messages passed by the view
		
		// View either wants to add a new user or load an existing user's data
		if (arg1.getClass().equals(User.class)) {
			User user = (User)arg1;
			
			String name = user.getName();
			
			// verify that the user name is within the defined limit
			if (name.length() > nameLimit) {
				View.displayWarningMessage("The user name " + name + 
						" exceeds the character limit of " + nameLimit + " characters.\n" + 
						"Name will be truncated to " + name.substring(0, nameLimit));
				name = name.substring(0, nameLimit);
				user.setName(name);
			}
			
			// the requested user exists in the model
			if (model.isExistingUser(user.getName())){
				
				// do not open the main window if it has been done before
				this.handleChangeUserRequest(user.getName());
				if (model.hasActiveUser()) {
					view.showMainView();
				}
			} else {
				// New user, so ask if it should be created
				Enum<Answer> a = view.displayYesNoMessage("User does not exist, would you like to create this user?");
				
				// create the user and show main view
				if (a == Answer.YES) {
					this.handleCreateUserRequest(user.getName(), new Preferences(user.getName()));
					view.showMainView();
				} else if (a == Answer.NO) {
					view.showUserLoginView();
				}
			}
		}
		
		// either want to add or delete a weight
		if (arg1.getClass().equals(WeightEntry.class)) {
			WeightEntry entry = (WeightEntry)arg1;
			
			// user wants to delete this weight entry...
			if (entry.getDeleteFlag()) {
				this.handleDeleteWeightRequest(entry);
				
			// user wants to add an entry
			} else {
				this.handleAddWeightRequest(entry);
			}
		}
		
		// preferences are changing...
		if (arg1.getClass().equals(Preferences.class)) {
			Preferences prefs = (Preferences)arg1;
			handlePreferencesChangeRequest(prefs);
		}
		
		// quit cleanly...
		if (arg1.getClass().equals(String.class)) {
			handleQuitApplicationRequest();
		}
	}
	
	
	/**
	 * Invoked when a delete WeightEntry request has been received from the View
	 * 
	 * @param entry The weight entry to delete
	 */
	public void handleDeleteWeightRequest(WeightEntry entry) {
		// Prompt the user
		Enum<Answer> a = view.displayYesNoMessage("Do you really want to delete this weight entry?");
		
		// delete the entry
		if (a == Answer.YES) {
			try{
				model.deleteWeight(entry.getDate());
			} catch (Exception e) {
				View.displayErrorMessage(e.getMessage());
			}
		}
		// do nothing if No is selected
	}
	
	/**
	 * This method is invoked when the view has passed a message to the controller that
	 * the end user is trying to add a new weight entry.
	 * 
	 * @param entry The weight entry to be added
	 */
	public void handleAddWeightRequest(WeightEntry entry) {
		Calendar currentDate = new GregorianCalendar();
		Calendar date = entry.getDate();
		double weight = entry.getWeight();
		
		// check if the date argument is in the future
		if (Controller.compareDates(currentDate, date) < 0) {
			View.displayErrorMessage("The specified date " + Controller.getDateString(date) + " is in the future.");
			return;
		}
		
		// verify weight is not negative or 0
		if (weight < 0) {
			View.displayErrorMessage("Weight cannot be negative or zero.");
			return;
		}
		
		// make sure there is no existing data entry for this date
		if (model.hasExistingWeightEntry(date)) {

			// Prompt the user
			Enum<Answer> a = view.displayYesNoMessage("There is already a weight entry for " + Controller.getDateString(date) +
					".  Would you like to overwrite this entry?");

			// delete it
			if (a == Answer.YES) {
				try{
					model.deleteWeight(entry.getDate());
				} catch (Exception e) {
					View.displayErrorMessage(e.getMessage());
				}
				System.out.println("YES");
			} else {
				// otherwise, do nothing
				System.out.println("NO");
				return;
			}
		} 
		
		// try and add the weight entry to the model
		try {
			model.addWeight(date, weight);
			System.out.println("Weight Added");
		} catch (Exception e) {
			View.displayErrorMessage("Error: " + e.getMessage());
		}
	}
	
	/**
	 * This method is invoked when the view has passed the controller a create user message.  It
	 * attempts to create a new user in the model.
	 * 
	 * @param name The user name of the user to create
	 * @param prefs The preferences for this user
	 */
	public void handleCreateUserRequest(String name, Preferences prefs) {
		// check to see if the user already exists
		if (model.isExistingUser(name)) {
			View.displayErrorMessage("The user name " + name + " already exists.");
			return;
		}
		
		// try and create the user
		try {
			model.createUser(name, prefs);
		} catch (Exception e) {
			View.displayErrorMessage("Error: " + e.getMessage());
		}
		
	}
	
	/**
	 * This method is invoked when the view has passed the controller a change user message.  It
	 * attempts to create a new user in the model.
	 * 
	 * @param name The user name to change to
	 * @throws InvalidInputException Thrown if the user name is invalid or already exists
	 * @throws Exception This exception is initially thrown by the model indicating that something
	 * went wrong with changing the user.  It is re-thrown up to the view so that an error message can
	 * be displayed.
	 */
	public void handleChangeUserRequest(String name) {
		// make sure the user exists..
		if (!model.isExistingUser(name)) {
			View.displayErrorMessage("User " + " does not exist.");
			return;
		}
		
		try {
			model.setCurrentUser(name);
		} catch (Exception e) {
			View.displayErrorMessage("Error: " + e.getMessage());
		}
	}
	
	
	/**
	 * This method is invoked when the view has passed the controller a change preferences message.  It
	 * attempts to update the preferences for the active user in the model
	 * 
	 * @param pref New preferences for the active user
	 */
	public void handlePreferencesChangeRequest(Preferences pref) {
		String name = pref.getName();
		
		// verify that the user name is within the defined limit
		if (name.length() > nameLimit) {
			View.displayWarningMessage("The user name " + name + 
					" exceeds the character limit of " + nameLimit + " characters.\n" + 
					"Name will be truncated to " + name.substring(0, nameLimit));
			name = name.substring(0, nameLimit);
			pref.setName(name);
		}
		
		// only update the DB with new user name if the name field changed
		if (!name.equals(model.getUserName())) {
			
			// dont allow a change to an existing user name
			if (model.isExistingUser(name)) {
				View.displayErrorMessage(name + " already exists in the database.\n" + 
						"User name will remain " + model.getUserName());
				pref.setName(model.getUserName());
			} else {
				model.setUserName(name);
			}
		}
		model.setPreferences(pref);
	}
	
	/**
	 * This method is invoked when the view has passed the controller a quit application message.  
	 * This will cause the controller to gracefully shut down the model, then exit.
	 */
	public void handleQuitApplicationRequest() {
		model.shutdown();
		System.exit(0);
	}
	
	/**
	 * Return a String representation of the specified date
	 * 
	 * @param date The date to convert to string
	 * @return Date in <YYYY>/<MM>/<DD> format
	 */
	public static String getDateString(Calendar date) {
		return (date.get(Calendar.MONTH) + 1) + "/" + date.get(Calendar.DAY_OF_MONTH) + "/" + date.get(Calendar.YEAR);
	}
	
	/**
	 * Compares two dates to each other.  This only compares the YEAR, MONTH, and DAY_OF_MONTH
	 * fields of each parameter.
	 * 
	 * @param date1 First date to compare
	 * @param date2 Second date to compare
	 * @return If date1 is before date2, returns -1.  If date1 and date2 are the same, return 0.
	 * If date1 is after date2, return 1
	 */
	public static int compareDates(Calendar date1, Calendar date2) {
		// get the relevant information from date1 and date2
		int d1_year = date1.get(Calendar.YEAR);
		int d1_month = date1.get(Calendar.MONTH);
		int d1_day = date1.get(Calendar.DAY_OF_MONTH);
		int d2_year = date2.get(Calendar.YEAR);
		int d2_month = date2.get(Calendar.MONTH);
		int d2_day = date2.get(Calendar.DAY_OF_MONTH);
		
		// get differences
		int year_diff = d1_year - d2_year;
		int month_diff = d1_month - d2_month;
		int day_diff = d1_day - d2_day;
		int retVal;
		
		// if the years are equal
		if (year_diff == 0) {
			// if the months are equal
			if (month_diff == 0) {
				// if the days are equal, the dates are equal
				if (day_diff == 0) {
					retVal = 0;
				// date 1 is before date2
				} else if (day_diff < 1) {
					retVal = -1;
				// date1 is after date2
				} else {
					retVal = 1;
				}
				// date 1 is before date2	
			} else if (month_diff < 0) {
				retVal = -1;
			// date1 is after date2
			} else {
				retVal = 1;
			}
		// date 1 is before date2
		} else if (year_diff < 0) {
			retVal = -1;
		// date1 is after date2
		} else { 
			retVal = 1;
		}
		return retVal;		
	}
}
